﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;

using Bling.Core;
using Bling.WPF;
using Bling.WPF.Util;
using Bling.Slides;
using Bling.Util;
using Bling.Shaders;
using Bling.Graphics;
namespace Bling.Example {
  using Bling.Matrices;
  using Bling.WPF3D;
  using Bling.DX;
  public class ParametricNoInference : HasHost {

    public interface VertexSignature : ISignature {
      [Semantics.TexCoord()]
      PointBl UV { get; set; }
    }
    public interface PixelSignature : VertexSignature, IPixelInput { }

    public interface UniformSignature : ISignature {
      [Semantic("PHASE")]
      DoubleBl Phase { get; set; }
      [Semantic("FREQ")]
      DoubleBl Frequency { get; set; }
      [Semantic("MAGN")]
      DoubleBl Magnitude { get; set; }
      [Semantic("Position")]
      Point3DBl CameraPosition { get; set; }
      [Semantic("Texture")]
      TextureBl Texture { get; set; }
      [Semantic("World")]
      MatrixBl<D4, D4> World { get; set; }
      [Semantic("ProjectionView")]
      MatrixBl<D4, D4> ProjectionView { get; set; }
    }


    private static readonly Uri AutumnUri = "Resources/Autumn.jpg".MakePackUri(typeof(ParametricNoInference));
    //private static readonly Uri ParticleUri = "Resources/particle.png".MakePackUri(typeof(Example1));

    public ParametricNoInference() : base(false) {
      // first, create some sliders to control the simulation.
      LabelSliderBl Phase = new LabelSliderBl(this) {
        LabelName = "Phase",
        ZIndex = 100,
      };
      LabelSliderBl Frequency = new LabelSliderBl(this) {
        LabelName = "Frequency",
        Minimum = 0,
        Maximum = 40d,
        Value = 10d,
        ZIndex = 100,
        LeftTop = Phase.LeftBottom,
      };
      LabelSliderBl Magnitude = new LabelSliderBl(this) {
        LabelName = "Magnitude",
        Minimum = 0,
        Maximum = 0.5, Value = 0.05,
        ZIndex = 100,
        LeftTop = Frequency.LeftBottom,
      };

      var RotateEye = new LabelSliderBl(this) { LeftTop = Magnitude.LeftBottom, LabelName = "Eye", Minimum = 0, Maximum = 360, Value = 0 };
      var RotateWorld = new LabelSliderBl(this) { LeftTop = RotateEye.LeftBottom, LabelName = "World", Minimum = 0, Maximum = 360, Value = 0 };

      // world matrix specifies where a render object will go in the world. Just set it to the default for now.
      MatrixBl<D4, D4> World = MatrixBl<D4, D4>.Identity;
      // If we wanted to rotate the object in the world
      World = RotateWorld.Value.ToDegrees().AsRadians.RotateAround(new Point3DBl(1d, 1d, 0d)).Matrix;
      //  world = world * sliderY.Value.PI2().RotateAround(new Point3DBl(sliderZ, 1d, 1d));

      // represents where our eye is in the world.
      Point3DBl Center = new Point3DBl(0, 0, 0);
      //Point3DBl Eye = Center + new Point3DBl((rotateX0.Value + 90).ToDegrees().ToPoint.X * moveZ.Value, 0, (rotateX0.Value + 90).ToDegrees().ToPoint.Y * moveZ.Value);     

      // the 3D cordinate system:
      //  Y
      //  |   Z
      //  |  /
      //  | /
      //  |/
      // O -------- X
      // Configure a WPF perspective camera.
      PerspectiveCameraCl Camera = new PerspectiveCameraCl() {
        Position = Center + 
          ((RotateEye.Value + 90).ToDegrees().ToPoint * -4).InsertY(0),
        Direction = { Look = Center }, FieldOfView = .25.PI().AsDegrees,
      };

      // Create a sphere displaced by an egg crate height field. 
      // The egg crate's frequency, magnitude, and phase are controlled
      // by WPF sliders.

      var Effect = new Effect<UniformSignature>();
      var Technique = Effect.Make<VertexSignature, PixelSignature>(
        (Uniforms0, input, output, vertex) => {
          output.UV = input.UV;
          PSurface Surface = GraphicDefinitions.Sphere.Displace(GraphicDefinitions.EggCrate.Adjust(Uniforms0.Frequency, Uniforms0.Magnitude, Uniforms0.Phase * .25));
          output.Position = (Uniforms0.ProjectionView * Uniforms0.World * Surface)[input.UV];
        }, (Uniforms0, input, pixel) => {
          PSurface Surface = GraphicDefinitions.Sphere.Displace(GraphicDefinitions.EggCrate.Adjust(Uniforms0.Frequency, Uniforms0.Magnitude, Uniforms0.Phase * .25));
          Point3DBl Normal = ((Point3DBl)(Uniforms0.World * Surface.Normals[input.UV])).Normalize;
          Point3DBl Position = (Point3DBl)(((Uniforms0.World) * Surface[input.UV]));
          // Create lighting by addition that we want to use on the color.
          DiffuseMaterialCl diffMaterial = new DiffuseMaterialCl() {
            Ambient = { Factor = 1 },
            Factor = 1,
          };
          SpecularMaterialCl specMaterial = new SpecularMaterialCl() { Power = 3, Factor = 1, };
          // Light definitions borrowed from WPF 3D.
          AmbientLightCl ambLight = new AmbientLightCl() {
            Color = new ColorBl(0.5, 0.5, 0.5),
          };
          SpotLightCl spotLight = new SpotLightCl() {
            Direction = (new Point3DBl(-1, 0, -1)).Normalize,
            Color = new ColorBl(0.5, 0, 0),
            Position = new Point3DBl(5, 0, 5),
            InnerConeAngle = 2.ToDegrees(),
            OuterConeAngle = 4.ToDegrees(),
          };
          DirectionalLightCl dirLight = new DirectionalLightCl() {
            Direction = (new Point3DBl(-1, 0, -1)).Normalize,
            Color = new ColorBl(00, 0.5, 0.5),
            //Color = new ColorBl(1, 1, 1),
          };
          Lighting L = (diffMaterial + specMaterial).ApplyLights(ambLight + spotLight + dirLight);
          // Apply lighting, assign vertex color.
          // sample the Autumn texture using the current parametric point. The range of uv
          // is [-.5,-.5] to [+.5,+.5] while the range of a texture sample is [0,0] to [1,1].
          // As a result, we add a half a unit (0.5) to uv before sampling. 
          // Note that this computation will occur in the pixel shader where uv will be properly 
          // interpolated.
          pixel.Color = L.ApplyAt(Position, Normal, Uniforms0.CameraPosition).ForPixel(Uniforms0.Texture, input.UV + 0.5);
        }, (pass) => { }
      );
      int NWidth = 100;
      int NHeight = 100;
      var Samples = new PSurfaceSample(NWidth, NHeight);
      var Vertices = new UserBuffer<VertexSignature, Topology.TriangleKind, Topology.ListOrg>() {
        VertexCount = NWidth * NHeight,
        Indices = new IndexBuffer<Topology.TriangleKind,Topology.ListOrg>(),
        F0 = (vid, input) => {
          input.UV = Samples[vid];
        },
      };
      Func<int, int, int> fN = (i, j) => (j % NHeight) * NWidth + (i % NWidth);
      for (int i = 0; i < NWidth - 1; i++) {
        for (int j = 0; j < NHeight - 1; j++) {
          Vertices.Indices.AddTo(fN(i + 0, j + 0), fN(i + 0, j + 1), fN(i + 1, j + 0));
          Vertices.Indices.AddTo(fN(i + 1, j + 1), fN(i + 1, j + 0), fN(i + 0, j + 1));
        }
      }
      // intialize uniforms, configure device. 
      {
        var UniformsManager = new DefaultSignatureManager2();
        TextureBl Autumn = new ImageTexture<D2>(AutumnUri);
        
        var Uniforms = ShaderSigs.Generate<UniformSignature>(UniformsManager);
        Uniforms.World = World;
        Uniforms.ProjectionView = Camera.Project * Camera.View;
        Uniforms.Frequency = Frequency;
        Uniforms.Phase = Phase;
        Uniforms.Magnitude = Magnitude;
        Uniforms.CameraPosition = Camera.Position;
        Uniforms.Texture = Autumn;
        //Uniforms.Texture = Particle;

        Device.AddRender(new RenderEntry<UniformSignature, VertexSignature>() {
          Technique = Technique, Target = Device.Display,
          Buffer = Vertices, UniformsManager = UniformsManager,
        });
      };
      Host.LeftTop = 0d;
      Host.Size = 500d;
      // sliders go beneath the host.
      Phase.LeftTop = Host.LeftBottom;
    }
  }
}
